<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="Liocx" id="Liocx"><span data-lake-id="u9f353174" id="u9f353174">典型回答</span></h1>
  <p data-lake-id="ue9b2e63b" id="ue9b2e63b"><br></p>
  <p data-lake-id="udb8ab115" id="udb8ab115"><br></p>
  <p data-lake-id="u13c1b72e" id="u13c1b72e"><span data-lake-id="u69544e35" id="u69544e35">数据库加密可以保障数据的安全，但是也会带来很多的问题，其中有一个比较关键的就是数据的模糊查询的问题。</span></p>
  <p data-lake-id="ub35a1144" id="ub35a1144"><span data-lake-id="u42479291" id="u42479291">​</span><br></p>
  <p data-lake-id="u3e4cbc80" id="u3e4cbc80"><span data-lake-id="u6ea7cd35" id="u6ea7cd35">当我们通过加密后把密文存到数据库中的时候，在通过明文进行模糊查询是不生效的。</span></p>
  <p data-lake-id="uf71ad2ce" id="uf71ad2ce"><span data-lake-id="u945fbb0e" id="u945fbb0e">​</span><br></p>
  <p data-lake-id="u5705317b" id="u5705317b"><span data-lake-id="uf3b49db8" id="uf3b49db8">比如</span><code data-lake-id="u8032d5b5" id="u8032d5b5"><span data-lake-id="u03d163fd" id="u03d163fd">Hollis</span></code><span data-lake-id="u3d19154a" id="u3d19154a">加密后的内容是</span><code data-lake-id="ud8380ecc" id="ud8380ecc"><span data-lake-id="uc408b4b2" id="uc408b4b2">363164846D8200899E314897E64A7420</span></code><span data-lake-id="u250fac77" id="u250fac77">，那么当我想用</span><code data-lake-id="ude6bbefb" id="ude6bbefb"><span data-lake-id="u6d71b248" id="u6d71b248">Ho</span></code><span data-lake-id="u215cf3bd" id="u215cf3bd">来做模糊查询时候，那么他的密文是</span><code data-lake-id="ue68b45e1" id="ue68b45e1"><span data-lake-id="u0b2cd5dc" id="u0b2cd5dc">71AAFD38484F3160708C6A6D2D5F736B</span></code><span data-lake-id="u72081eac" id="u72081eac">，这两个密文可以说是没有任何关系的，所以，是无法直接做模糊查询的。那么如何解决这个问题呢？</span></p>
  <p data-lake-id="u7c319912" id="u7c319912"><span data-lake-id="u4c98b9cf" id="u4c98b9cf">​</span><br></p>
  <h3 data-lake-id="Mobia" id="Mobia"><span data-lake-id="u45bacc16" id="u45bacc16">先解密再查询</span></h3>
  <p data-lake-id="ud3564824" id="ud3564824"><br></p>
  <p data-lake-id="uddbfe570" id="uddbfe570"><span data-lake-id="uc9e8c20d" id="uc9e8c20d">一种比较常见的方法，就是把要查询的表中的所有符合条件的数据，都加载到应用内存中，在内存中逐个解密，然后再做模糊匹配。</span></p>
  <p data-lake-id="u456a2e2f" id="u456a2e2f"><span data-lake-id="u2f46608b" id="u2f46608b">​</span><br></p>
  <p data-lake-id="u6d1b9618" id="u6d1b9618"><span data-lake-id="u362fed43" id="u362fed43">这个方案的优点就是实现简单，缺点也很明显，需要把所有数据都加载到内存中，容易导致OOM。</span><strong><span data-lake-id="u7b43aa8b" id="u7b43aa8b">不推荐</span></strong></p>
  <p data-lake-id="u9e04803d" id="u9e04803d"><strong><span data-lake-id="ue2f4df73" id="ue2f4df73">​</span></strong><br></p>
  <h3 data-lake-id="mgwjT" id="mgwjT"><span data-lake-id="ua472673d" id="ua472673d">明文映射表</span></h3>
  <p data-lake-id="udc7382cb" id="udc7382cb"><br></p>
  <p data-lake-id="u1893e843" id="u1893e843"><span data-lake-id="ua10e54fd" id="ua10e54fd">还有人提出过说单独建一张表，其中保存明文和目标表之间的映射，需要模糊查询的时候先去明文映射表中查到主键，然后再去目标表查询数据。</span></p>
  <p data-lake-id="uc90e30e0" id="uc90e30e0"><span data-lake-id="u7bf61143" id="u7bf61143">​</span><br></p>
  <p data-lake-id="u042cc7cd" id="u042cc7cd"><span data-lake-id="u1ff8fb21" id="u1ff8fb21">但是这个方案基本上是属于自欺欺人，因为一旦数据被拖库，还是会丢。</span><strong><span data-lake-id="ubab84a1f" id="ubab84a1f">不推荐</span></strong></p>
  <p data-lake-id="u634946f0" id="u634946f0"><strong><span data-lake-id="u51d5a057" id="u51d5a057">​</span></strong><br></p>
  <p data-lake-id="ud7fdc0aa" id="ud7fdc0aa"><strong><span data-lake-id="u4acaa9dd" id="u4acaa9dd">​</span></strong><br></p>
  <h3 data-lake-id="ypCpL" id="ypCpL"><span data-lake-id="u976881a2" id="u976881a2">数据库解密函数</span></h3>
  <p data-lake-id="u20cfbc97" id="u20cfbc97"><br></p>
  <p data-lake-id="uf4f06164" id="uf4f06164"><span data-lake-id="uf373c01f" id="uf373c01f">加密的时候如果用了函数的话，解密的时候我们也可以借助函数来做解密，同时做模糊查询，比如加密时使用了AES_ENCRYPT算法：</span></p>
  <p data-lake-id="u13d59fc5" id="u13d59fc5"><span data-lake-id="u73c09524" id="u73c09524">​</span><br></p>
  <pre lang="java"><code>
-- 加密数据
INSERT INTO user_data (username, credit_card) VALUES ('Hollis', AES_ENCRYPT('1234-5678-9012-3456', 'hollis_secret_key'));
</code></pre>
  <p data-lake-id="u005aed25" id="u005aed25"><span data-lake-id="u85a3cbc4" id="u85a3cbc4">​</span><br></p>
  <p data-lake-id="u899d6be4" id="u899d6be4"><span data-lake-id="u3a350573" id="u3a350573">​</span><br></p>
  <p data-lake-id="u6272f3a8" id="u6272f3a8"><span data-lake-id="u2e028544" id="u2e028544">那么在做模糊查询的时候就可以这样做：</span></p>
  <p data-lake-id="u75b6d8e2" id="u75b6d8e2"><span data-lake-id="u0d2ee3b3" id="u0d2ee3b3">​</span><br></p>
  <pre lang="java"><code>
SELECT * FROM user_data WHERE AES_DECRYPT(credit_card, 'hollis_secret_key') like 'Hol%';
</code></pre>
  <p data-lake-id="uade52118" id="uade52118"><br></p>
  <p data-lake-id="u0e5cff80" id="u0e5cff80"><span data-lake-id="u6487f79e" id="u6487f79e">这样也就能实现一个模糊查询的效果了，但是这个方案有个缺点，就是无法用到索引，不是因为用like，而是因为我们在字段上用了函数，索引就会失效。</span></p>
  <p data-lake-id="uc9792cdb" id="uc9792cdb"><span data-lake-id="u260dac18" id="u260dac18">​</span><br></p>
  <p data-lake-id="u77c54f5e" id="u77c54f5e"><span data-lake-id="uee963a66" id="uee963a66">这个方案适合于表中数据量不大，或者查询条件中还有其他查询字段可以走索引的情况。</span></p>
  <p data-lake-id="u65d5992f" id="u65d5992f"><span data-lake-id="u8beef666" id="u8beef666">​</span><br></p>
  <h3 data-lake-id="cy8qf" id="cy8qf"><span data-lake-id="u73e89532" id="u73e89532">明文分词</span></h3>
  <p data-lake-id="u33095068" id="u33095068"><br></p>
  <p data-lake-id="u74bc2bc7" id="u74bc2bc7"><span data-lake-id="u4ede46b8" id="u4ede46b8">首先有一个比较简单的做法，那就是对明文进行分词，然后分别加密后存储到数据库中，比如</span><code data-lake-id="u726d648f" id="u726d648f"><span data-lake-id="uf52b9ae4" id="uf52b9ae4">Hollis</span></code><span data-lake-id="ucec71fa6" id="ucec71fa6">这个需要加密的字符串，我们就可以把他拆成</span><code data-lake-id="uf5849835" id="uf5849835"><span data-lake-id="u2ea10d7b" id="u2ea10d7b">Ho</span></code><span data-lake-id="u2beee42e" id="u2beee42e"> 、</span><code data-lake-id="u3250779c" id="u3250779c"><span data-lake-id="u358744fd" id="u358744fd">Holl</span></code><span data-lake-id="u966cd6b9" id="u966cd6b9">、</span><code data-lake-id="u85ca61ce" id="u85ca61ce"><span data-lake-id="u2944b74a" id="u2944b74a">llis</span></code><span data-lake-id="u3ee80239" id="u3ee80239">等这几个字符串，然后分别对他们进行加密，并保存到数据库中。</span></p>
  <p data-lake-id="uac563c80" id="uac563c80"><span data-lake-id="ud94fa8c0" id="ud94fa8c0">​</span><br></p>
  <p data-lake-id="uf5bec203" id="uf5bec203"><span data-lake-id="u0f7c4a66" id="u0f7c4a66">这样当我们使用</span><code data-lake-id="ud8dfd2dd" id="ud8dfd2dd"><span data-lake-id="u3e329f21" id="u3e329f21">Ho</span></code><span data-lake-id="uaca21c7a" id="uaca21c7a"> 、</span><code data-lake-id="u64c2fd57" id="u64c2fd57"><span data-lake-id="ud20e688d" id="ud20e688d">Holl</span></code><span data-lake-id="u6a954199" id="u6a954199">、</span><code data-lake-id="ueea1cab2" id="ueea1cab2"><span data-lake-id="uafc2ac26" id="uafc2ac26">llis</span></code><span data-lake-id="uba8929d1" id="uba8929d1"> 进行查询的时候，就可以对明文加密后去数据库中匹配了。</span></p>
  <p data-lake-id="uba21b91b" id="uba21b91b"><span data-lake-id="ua1ffdbb0" id="ua1ffdbb0">​</span><br></p>
  <p data-lake-id="ub10db7f7" id="ub10db7f7"><span data-lake-id="u89b7f98d" id="u89b7f98d">这个方案的缺点也比较明显，第一个就是需要冗余很多字段，第二个就是不够灵活，如果我按照</span><code data-lake-id="u8193b280" id="u8193b280"><span data-lake-id="ufabf565e" id="ufabf565e">Holli</span></code><span data-lake-id="u1f925744" id="u1f925744">来查询的话就不支持了。</span></p>
  <p data-lake-id="u083d5c8e" id="u083d5c8e"><span data-lake-id="u341646c0" id="u341646c0">​</span><br></p>
  <p data-lake-id="u90932014" id="u90932014"><span data-lake-id="ua9a8539e" id="ua9a8539e">当然，也有一些改进的方式，比如并不需要增加多个字段，可以把这些需要用于模糊查询的信息都放到同一个字段中，如DECRYPT_NAME，拼接成一个字符串就行了。如</span><code data-lake-id="u3e27d8b2" id="u3e27d8b2"><span data-lake-id="u762df8b5" id="u762df8b5">71AAFD38484F3160708C6A6D2D5F736B,83B01A578395CE81AEAAC6A4FE70AA94,E90048FB068AA98B7EC751CBD6DC78B7</span></code><span data-lake-id="u1fdf6ccd" id="u1fdf6ccd"> 这样就只需要通过这个字段做模糊查询就行了。SQL如下：</span></p>
  <p data-lake-id="ue6367bf8" id="ue6367bf8"><span data-lake-id="u2753b2e8" id="u2753b2e8">​</span><br></p>
  <pre lang="java"><code>
SELECT *
FROM hollis_test_table
WHERE DECRYPT_NAME REGEXP '71AAFD38484F3160708C6A6D2D5F736B|83B01A578395CE81AEAAC6A4FE70AA94|E90048FB068AA98B7EC751CBD6DC78B7';
</code></pre>
  <p data-lake-id="u9b9d6e62" id="u9b9d6e62"><br></p>
  <p data-lake-id="u10aca139" id="u10aca139"><br></p>
  <p data-lake-id="u630752d3" id="u630752d3"><span data-lake-id="u6c5ee13a" id="u6c5ee13a">很多公司其实都是用的这种方案，比如：</span></p>
  <p data-lake-id="ud2b8f4cf" id="ud2b8f4cf"><span data-lake-id="u54a723d7" id="u54a723d7">​</span><br></p>
  <p data-lake-id="ue3cb29d7" id="ue3cb29d7"><span data-lake-id="u042358b0" id="u042358b0">淘宝：</span><a href="https://open.taobao.com/docV3.htm?docId=106213&amp;docType=1" target="_blank" data-lake-id="u1383eb2c" id="u1383eb2c"><span data-lake-id="u2df437cd" id="u2df437cd">https://open.taobao.com/docV3.htm?docId=106213&amp;docType=1</span></a></p>
  <p data-lake-id="uc93ef9ad" id="uc93ef9ad"><span data-lake-id="u3f27d069" id="u3f27d069">​</span><br></p>
  <p data-lake-id="u99f6679c" id="u99f6679c"><span data-lake-id="u33b53433" id="u33b53433">拼多多：</span><a href="https://open.pinduoduo.com/application/document/browse?idStr=3407B605226E77F2" target="_blank" data-lake-id="u2992e134" id="u2992e134"><span data-lake-id="uf2dfecb8" id="uf2dfecb8">https://open.pinduoduo.com/application/document/browse?idStr=3407B605226E77F2</span></a></p>
 </body>
</html>